﻿using System.Collections.Generic;
using System.Linq;

namespace Borg.Framework
{
    public class MenuManager
    {
        private readonly SlotInfo _actionSlot;
        private readonly IMenuStrip _menuStrip;
        private readonly MenuUpdater _menuUpdater;
        private readonly SlotInfo _presentationViewSlot;
        private readonly SlotInfo _viewSlot;

        public MenuManager(IMenuStrip menuStrip, SlotInfo viewSlot, SlotInfo actionSlot, SlotInfo toolsSlot)
            : this(menuStrip, viewSlot, null, actionSlot, toolsSlot)
        { }

        public MenuManager(IMenuStrip menuStrip, SlotInfo viewSlot,
            SlotInfo presentationViewSlot, SlotInfo actionSlot, SlotInfo toolsSlot)
        {
            _menuStrip = menuStrip;
            _viewSlot = viewSlot;
            _presentationViewSlot = presentationViewSlot;
            _actionSlot = actionSlot;
            
            if (viewSlot != null)
            {
                menuStrip.SetCreateBehavior(viewSlot, GetViewMenuPath);
                menuStrip.SetSelectBehavior(viewSlot, GetWindowMenuPath);
                
                viewSlot.SelectionChanged += RegisterTaggedMenuItems;
            }

            if (presentationViewSlot != null)
                menuStrip.SetCreateBehavior(presentationViewSlot, GetViewMenuPath);

            menuStrip.SetActionBehavior(actionSlot, GetMenuItemPath);

            if (toolsSlot != null)
                menuStrip.SetCreateBehavior(toolsSlot, GetToolsMenuPath);

            _menuUpdater = new MenuUpdater(actionSlot, "Tag");
        }

        public void CloseMenu()
        {
            if (_viewSlot != null)
            {
                _menuStrip.RemoveCreateBehavior(_viewSlot);
                _menuStrip.RemoveSelectBehavior(_viewSlot);
            }

            if (_presentationViewSlot != null)
                _menuStrip.RemoveCreateBehavior(_presentationViewSlot);

            _menuStrip.RemoveActionBehavior(_actionSlot);
        }

        private static string GetMenuItemPath(PlugTypeInfo plugType)
        {
            return (string)plugType.GetParamValue("MenuItem");
        }

        private static string GetToolsMenuPath(PlugTypeInfo plugType)
        {
            return MenuItemInfo.GetMenuPath("&Tools", 0.6f, plugType);
        }

        private static string GetViewMenuPath(PlugTypeInfo plugType)
        {
            return MenuItemInfo.GetMenuPath("&View", 0.4f, plugType);
        }

        private static string GetWindowMenuPath(PlugInfo plug)
        {
            return MenuItemInfo.GetMenuPath("&Window", 0.5f, plug);
        }

        private void RegisterTaggedMenuItems(object sender, SelectionChangedEventArgs args)
        {
            if (!Runtime.IsCompositionAllowed)
                return;

            _menuUpdater.RemoveMenuEntriesOf(args.RemovedPlugs);
            _menuUpdater.AddMenuEntriesOf(args.AddedPlugs);
        }

        private class MenuUpdater
        {
            private readonly string _paramName;
            private readonly SlotInfo _slot;

            public MenuUpdater(SlotInfo foreignSlot, string param)
            {
                _slot = foreignSlot;
                _paramName = param;
            }

            public void AddMenuEntriesOf(IEnumerable<PlugInfo> addedPlugs)
            {
                foreach (var paramValue in addedPlugs.Select(plug =>
                    (string) plug.PlugTypeInfo.GetParamValue(_paramName)).Where(paramValue =>
                        !string.IsNullOrEmpty(paramValue)))
                    RegisterOnEqualParam(paramValue);
            }

            private void RegisterOnEqualParam(string primaryParam)
            {
                foreach (var plugType in
                    from plugType in Runtime.Repository.PlugTypeInfos.Find(_slot.Name)
                    where plugType.Qualify()
                    let paramValue = (string) plugType.GetParamValue(_paramName)
                    where paramValue.StartsWith(primaryParam)
                    select plugType)
                    _slot.Register(plugType);
            }

            public void RemoveMenuEntriesOf(IEnumerable<PlugInfo> removedPlugs)
            {
                foreach (var paramValue in removedPlugs.Select(plug =>
                    (string)plug.PlugTypeInfo.GetParamValue(_paramName)).
                    Where(paramValue => paramValue.Length != 0))
                {
                    for (var i = _slot.RegisteredPlugTypeInfos.Count - 1; i >= 0; i--)
                    {
                        var pti = _slot.RegisteredPlugTypeInfos[i];
                        var paramVal = (string)pti.GetParamValue(_paramName);

                        if ((paramVal.Length != 0) && paramVal.StartsWith(paramValue))
                            _slot.Deregister(pti);
                    }
                }
            }
        }
    }
}

